//
// File peer.h
//
// This file defines the four public methods of class Peer, together 
// with associated helper classes and constants
//

// [x] Creating sockets
// [x] Connecting to any other peer
// [x] Reading the Peer List File and get all the peers
// [x] Reading the file to be read and storing into local mem 
// [x] Making a separate thread for each peer 
// [x] Making a thread for reading from terminal 
// [ ] Making a thread for listening to other peers
// [ ] Divide the files into chunks
// [ ] Transfer chunks to each peer keeping track of which is going where 
// [ ] Getting status of file transfer

#ifndef PEER_H
#define PEER_H

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <fcntl.h>


using namespace std;

class Status;
class Peers;
class Peer;

const int chunkSize = 65536; // In Bytes
const int packetSize = 1447; //2894; // 22 packets per chunk
int lastChunkSize = 0;
int totChunks = 0;
int chunksRec = 0;
int* chunkMap;
int* chunkOrder;
int* chunkRep;
int fileSize = 0;
char* fileBuffer;
char fileName[15];
const int maxPeers  = 6;
const int maxFiles  = 100;   
int max;
int peerC;
int * peerCMap;
int idx; // peer's own index

//char* PeersFile = "./ListPeers.txt";
char* PeersFile = "./testpeer.txt";
int ownerLeft = 0;
int xpeer = 0;
char fileList[maxFiles][15]; // filename size = 15 characters, and number of files = maxFiles
int mapConn[maxPeers] = {0, 0, 0, 0, 0, 0};

// Peers information
int totPeers = 0;
int actPeers = 0;
int actSockets[maxPeers];
int stopAccepting = 0;
Peer * peerList;
enum State { connected, disconnected, unknown } _state;
int fileMaxChk=0;
int fileOwner = 0;
pthread_t threads[6];// 2 extra threads for reading terminal and listening to other peers

pthread_mutex_t mutex1;
pthread_mutex_t mutex2;
pthread_mutex_t mutex3;
pthread_mutex_t mutex4;
pthread_mutex_t mutex5;
pthread_mutex_t mutex6;
pthread_mutex_t mutex7;

char * testport;
int insert(char* filename);
static void* connectSoc(void* returnVal);
static void* acceptSoc(void* returnVal);
static void* loadChunk(void* returnVal);
int numberOfFiles();
int fractionPresentLocally(int fileNumber);  // Use -1 to indicate if the file requested is not present
int fractionPresent(int fileNumber);         // Use -1 to indicate if the file requested is not present in the system
int   minimumReplicationLevel(int fileNumber); // Use -1 to indicate if the file requested is not present in the system
float averageReplicationLevel(int fileNumber); // Use -1 to indicate if the file requested is not present in the system

// socket info
int Socket = 0;
struct sockaddr_in srv_addr, cli_addr[maxPeers];
int Port = 0;

// Peers is a dumb container to hold the peers; the number of peers is fixed,
// but needs to be set up when a peer starts up; feel free to use some other
// container class, but that class must have a method that allows it to read
// the peersFile, since otherwise you have no way of having a calling entity 
// tell your code what the peers are in the system.

class Peers {
public:
    	int initialize(char * peersFile); // The peersFile is the name of a file that contains a list of the peers
                                	  // Its format is as follows: in plaintext there are up to maxPeers lines,
                                          // where each line is of the form: <IP address> <port number>
                                          // This file should be available on every machine on which a peer is started,
                                          // though you should exit gracefully if it is absent or incorrectly formatted.
                                          // After execution of this method, the _peers should be present.
    	//Peer operator()(int i);
	char* GetAddress(int index);
	int GetPort(int index);   
private:
    	int _numPeers;
    	Peer * _peers;
	FILE * fp;
	int init;
};

class Peer {
    
public:
	int initialize(char * peersFile, char* portNo);
    	int query(Status& status);
    	int join();     
    	int leave();
	static void* readTerminal(void* returnVal);
	char IPAddress[15];
	int PortNo;
	int index;
	State st;
private:
    	Peers _peers;
    	int filesize;
};

int getChunkPeerInfo( int sock, int index )
{
	int* peerInfo;
	peerInfo = (int* ) malloc(totChunks*sizeof(int));
	int i = 0;
	while( read(sock, peerInfo, totChunks*4) < 0 ); // reading the response
	for(i = 0; i < totChunks; i++)
	{
		pthread_mutex_lock( &mutex1 );
			chunkRep[(index*totChunks)+i] = peerInfo[i];
		pthread_mutex_unlock( &mutex1 );
	}
	free(peerInfo);	
	return 1;
}

int sendChunkPeerInfo( int sock )
{
	int* peerInfo;
	peerInfo = (int* ) malloc(totChunks*sizeof(int));
	int i = 0;
	for(i = 0; i < totChunks; i++)
	{
		pthread_mutex_lock( &mutex1 );
		 	peerInfo[i] = chunkRep[(idx*totChunks)+i];
		pthread_mutex_unlock( &mutex1 );
	}
	while( write(sock, peerInfo, totChunks*4) < 0);
	free(peerInfo);
	return 1;
}

int getLeastRepChunkIdx()
{
	int i, j;
	int cIdx = 0;
	int repVal = max-1; // starting by assuming that the least is all chunks	
	int tmpVal = 0;

	for (i = 0; i < totChunks; i++)
	{
		pthread_mutex_lock( &mutex1 );
		if(chunkMap[i] != 0)
		{
			pthread_mutex_unlock( &mutex1 );
			tmpVal = 0;
			for( j = 0; j < (max-1); j++)
			{
				pthread_mutex_lock( &mutex1 );
					tmpVal += chunkRep[j*totChunks+i];
				pthread_mutex_unlock( &mutex1 );
			}
			//printf("Least Replication: %d, i: %d, tmpVal: %d\n", repVal, i, tmpVal );
		}	
		else 
		{
			pthread_mutex_unlock(&mutex1 );
			tmpVal = -1; //chunk does not exist locally
		}
		if(tmpVal != -1 && (repVal > tmpVal)) 
		{
			repVal = tmpVal; cIdx = i;
		}
		tmpVal = 0;
	}
	return cIdx;
}

// This method is called when a peer is leaving
int Peer::leave()
{	
	int srvSocket = -1;
        int i = 0;
        char* IPAddress;
        int PortNo;
        struct sockaddr_in srvr_addr;
        struct hostent* server; 
        char buffer[20] = "default";
	State state;	

	while(1)
        { 
		// already looped through all the peers 
		if(i == max-1) break;
		
		pthread_mutex_lock( &mutex3 );
			state = peerList[i].st; // getting the current state of ith peer
		pthread_mutex_unlock( &mutex3 );
	
		// if the state is unknown then it means that the ith peer has left	
		// no need to go in the if statement, and connect to it
		//if( state != unknown )
                //{
			// creating a socket on this peer to connect to the ith peer
                        srvSocket = socket(AF_INET, SOCK_STREAM, 0); 
                        if( socket < 0 )
                                printf("error in opening the socket");
                
			pthread_mutex_lock( &mutex3 );
				// getting the IPAddress and PortNo of this peer
                        	IPAddress = peerList[i].IPAddress; 
                        	PortNo = peerList[i].PortNo;
			pthread_mutex_unlock( &mutex3 );
                
                        server = gethostbyname(IPAddress);
                        bzero((char*) &cli_addr[i], sizeof(cli_addr[i]));
                        cli_addr[i].sin_family = AF_INET; 
                        bcopy((char*) server->h_addr, (char*) &cli_addr[i].sin_addr.s_addr, server->h_length);  
                        cli_addr[i].sin_port = htons(PortNo);

			// checking if the ith peer is not the current peer itself
			// no need to connect to itself
                        if(PortNo != Port)      
                        {       
                                printf("Attempting to connect to Port: %d\n", PortNo);
                                while(connect(srvSocket, (struct sockaddr*) &cli_addr[i], sizeof(cli_addr[i])) < 0);
                        	
				// if the fileOwner is leaving then it will send unique exiting command to all the peers
				// the unique exiting command is 'o' (owner)
				if(fileOwner)					
				{
					buffer[0] = 'o';
					while( write(srvSocket, buffer, 19 ) < 0 );
				}
				else // otherwise if a non fileowner, it will send a general leaving msg 'l' (leave)
				{
					printf("Leaving!!\n");
					buffer[0] = 'l';
					while( write(srvSocket, buffer, 19 ) < 0 ); // notifying all the peers that this peer is leaving

					sprintf(buffer, "%d", i);
					while( write(srvSocket, buffer, 19 ) < 0 ); // sending the index of the peer that is leaving
				}   
                        }
                        close(srvSocket);
                        i++;
               // }
        }      
	// once done with notifying all the peers of exiting 
	// we need to stop accepting as well (exit accept thread) to close all the threads
	stopAccepting = 1; 
}

int numberOfFiles()
{
	return 1;
}

int fractionPresentLocally(int fileNumber)
{
	int cRec = 0;
	pthread_mutex_lock( &mutex2 );
		cRec = chunksRec;
	pthread_mutex_unlock( &mutex2 );
	return cRec;
}

int fractionPresent(int fileNumber)
{
	return totChunks;
}

int minimumReplicationLevel(int fileNumber)
{
	int i, j;
	int cIdx = 0;
	int repVal = max-1; // starting by assuming that the least is all chunks	
	int tmpVal = 0;

	for (i = 0; i < totChunks; i++)
	{
		pthread_mutex_lock( &mutex1 );
		if(chunkMap[i] != 0)
		{
			pthread_mutex_unlock( &mutex1 );
			tmpVal = 0;
			for( j = 0; j < (max-1); j++)
			{
				pthread_mutex_lock( &mutex1 );
					tmpVal += chunkRep[j*totChunks+i];
				pthread_mutex_unlock( &mutex1 );
			}
		}	
		else 
		{
			pthread_mutex_unlock(&mutex1 );
			tmpVal = -1; //chunk does not exist locally
		}
		if(tmpVal != -1 && (repVal > tmpVal)) 
		{
			repVal = tmpVal; cIdx = i;
		}
		tmpVal = 0;
	}
	return repVal;

}

float averageReplicationLevel(int fileNumber)
{
	int i, j;
	int cIdx = 0;
	int avgRep = 0; // starting by assuming that the least is all chunks	
	int tmpVal = 0;
	float value = 0.0;

	for (i = 0; i < totChunks; i++)
	{
		pthread_mutex_lock( &mutex1 );
		if(chunkMap[i] != 0)
		{
			pthread_mutex_unlock( &mutex1 );
			tmpVal = 0;
			for( j = 0; j < (max-1); j++)
			{
				pthread_mutex_lock( &mutex1 );
					tmpVal += chunkRep[j*totChunks+i];
				pthread_mutex_unlock( &mutex1 );
			}
			avgRep += tmpVal;
		}	
		else 
		{
			pthread_mutex_unlock(&mutex1 );
			avgRep += 0; //chunk does not exist locally
		}
	}
	
	value = (float) ((avgRep*10)/totChunks);
	value = value/10;
	return value;
}

// load the file chunk by chunk into the memory
void* loadChunk(void* returnVal)
{
    	FILE * fp;
	FILE * fp1;
	int cRec = 0;
	char fileN[10];
	int pChk = 1;
	char* fileBuf;
	int cSize = 0;
	printf("Entering Load file thread- FileOwner:%d\n", fileOwner);
	sprintf(fileN, "Result%d.mp3", idx);
	if(fileOwner)
	{
		pthread_mutex_lock( &mutex2 );
			cRec = chunksRec; // getting the chunksRec
		pthread_mutex_unlock( &mutex2 );
	
		
		printf("Chunks received:%d / Total chunks:%d\n", cRec, totChunks);
		pthread_mutex_lock( &mutex7 );
			// open a new file to pusht all the file data into that file
			fp = fopen(fileN, "w");
			fileMaxChk++;
			fclose(fp);
		pthread_mutex_unlock( &mutex7 );
		int i = 0;

		fp = fopen(fileN, "r+");
				fp1 = fopen(fileName, "r");
		for( i = 0; i < totChunks; i++ )				
		{
			if(i == totChunks-1) cSize =fileSize%chunkSize;		
			else cSize = chunkSize;
	
			fileBuf = (char* ) malloc(cSize*sizeof(char));	
			pthread_mutex_lock( &mutex7 );
				fileMaxChk++;
				// seek the file to next chunk
				fseek(fp1, (i*chunkSize), SEEK_SET);
				// read the next chunk into the buffer
				fread(fileBuf, sizeof(char), cSize, fp1);
				fseek(fp1, 0, SEEK_SET);
			pthread_mutex_unlock( &mutex7 );

			pthread_mutex_lock( &mutex7 );
				fileMaxChk++;
				fseek(fp, i*chunkSize, SEEK_SET);
				fwrite( fileBuf, sizeof(char), cSize, fp ); 
				fseek(fp, 0, SEEK_SET);
			pthread_mutex_unlock( &mutex7 );
			free(fileBuf);

			pthread_mutex_lock( &mutex1 );
				// update the chunkMap and chunkOrder showing that the (cRec)th chunk 
				// is loaded into the memory 
				chunkMap[i] = 1;	
				chunkRep[(idx*totChunks) + i] = 1;	
				chunkOrder[cRec] = cRec;
			pthread_mutex_unlock( &mutex1 );

			pthread_mutex_lock( &mutex2 );	
				// updating chunkRec 
				chunksRec++;
				cRec = chunksRec;
			pthread_mutex_unlock( &mutex2 );		
			
			//printf("Chunks received:%d / Total chunks:%d\n", cRec, totChunks);
		}
		fclose(fp);
				fclose(fp1);

		pthread_mutex_lock( &mutex6 );
			peerCMap[idx] = 1; // meaning the idxth peer has received all the chunks
		pthread_mutex_unlock( &mutex6 );
	}
	pthread_mutex_lock( &mutex6 );
		pChk = peerC;
	pthread_mutex_unlock( &mutex6 );

	while( pChk == 1)
	{	
		pthread_mutex_lock( &mutex6 );
			pChk = peerC;
		pthread_mutex_unlock( &mutex6 );
	}
}

// Thread designated just to read from the terminal to get user input
void * Peer::readTerminal(void * returnVal)
{
	char buffer[20];
	int i = 0;
	int min = 0;
	int local = 0;
	int present = 0;
	float avg = 0.0;	

	while(1)
	{
		printf("Reading from the terminal: ");
		fgets(buffer, 20, stdin); // getting user's input
	
		if(buffer[0] == 'i')
		{
			//if(threads[0] != NULL) pthread_join( threads[0], NULL);
			i = 0;
			while(buffer[i+2] != '.')
			{
				buffer[i] = buffer[i+2];
				i++;
	
				if(i == 19) 
				{
					printf("Incorrect file path\n");
					break;
				}
			}
			buffer[i] = buffer[i+2]; i++;
			buffer[i] = buffer[i+2]; i++;
			buffer[i] = buffer[i+2]; i++;
			buffer[i] = buffer[i+2]; i++;
			buffer[i] = '\0';
			printf("buffer: %s\n", buffer);
			insert(buffer);
		}
		
		if(buffer[0] == 'a')
			insert("");
	
		if(buffer[0] == 'm')
		{
			min = minimumReplicationLevel(1);
			printf("Transfer Status: Minimum Replication Level: %d\n", min);
		}
		
		if(buffer[0] == 'l')
		{
			local = fractionPresentLocally(1);
			printf("Transfer Status: Fractions Present Locally: %d Chunks (1 Chunk = %d Bytes)\n", local, chunkSize);
		}
		
		if(buffer[0] == 'p')
		{
			present = fractionPresent(1);
			printf("Transfer Status: Total # Of Fractions: %d Chunks (1 Chunk = %d Bytes)\n", present, chunkSize);
		}

		if(buffer[0] == 'r')
		{
			avg = averageReplicationLevel(1);
			printf("Transfer Status: Average Replication Level: %f\n", avg);
		}
	
		// 'q' = user wants to quit this peer
		if(buffer[0] == 'q')
			break;
	}	
	printf("Exiting!!\n");
	
	pthread_mutex_lock( &mutex4 );
		// setting xpeer notifies the connect thread to stop sending chunks to other peers 
		xpeer = 1;
	pthread_mutex_unlock( &mutex4 );
}

// This thread constantly connects and disconnects to each peer
// and sends the required chunk
void * connectSoc(void * returnVal)
{
	int srvSocket = -1;
	FILE * fp1;
	int i = 0;
	int j = 0;
	char* IPAddress;
	int PortNo;
	struct sockaddr_in srvr_addr;
	struct hostent* server;	
	int n = 0;
	char buffer[20];
	int msg = 0;
	int cRec = 0;
	State state;
	int cIdx;	
	char* fileBuf;
	int cSize = 0;
	int pSize = 0;
	int pIdx = 0;
	char fileN[10];
	sprintf(fileN, "Result%d.mp3", idx);
	while(1)
	{
		pthread_mutex_lock( &mutex7 );
			fp1 = fopen( fileN, "r" );
			if(fp1 == NULL) perror("nonzeor");
		pthread_mutex_unlock( &mutex7 );
		//test tes
		/*
		if(totChunks == 0 )
		{
			fp1 = fopen( fileN, "w" );
			if(fp1 == NULL) perror("zeor");
		}
		else
		{	
			fp1 = fopen( fileN, "r" );
			if(fp1 == NULL) perror("nonzeor");
		}
		*/
		if(i == max-1) i =0; // keep looping through all the peers
		
		pthread_mutex_lock( &mutex6 );
			//printf("peer1 stat: %d && peer2 stat: %d\n", peerCMap[0], peerCMap[1]);
			peerC = 0;
			for(j = 0; j < max-1; j++ )
			{	
				if( peerCMap[j] == 0 )
					peerC = 1;
			}		 
			if (peerC == 0) // all the peers have all the chunks
			{
				printf("Value of peerc: %d\n", peerC);
				printf("All the peers have received all the chunks!\n CLOSING!!\n");
				pthread_mutex_unlock( &mutex6 );		
				break;
			}
		pthread_mutex_unlock( &mutex6 );		

		pthread_mutex_lock( &mutex6 );
		if( peerCMap[i] != 1) // meaning if ith peer doesn't have all the chunks
		{
			pthread_mutex_unlock( &mutex6 );
			
			pthread_mutex_lock( &mutex2 );
			// checking if the chunksRec > 0 (have something to send)
			// and the state of ith peer is not unknown 
			if( (cRec = chunksRec) )
			{	
				pthread_mutex_unlock( &mutex2 );

				while((srvSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0);
				//if( srvSocket < 0 )
				//	printf("error in opening the socket");
			
				pthread_mutex_lock( &mutex3 );
					// getting the IPAddress and PortNo of the ith peer.
					IPAddress = peerList[i].IPAddress; 
					PortNo = peerList[i].PortNo;
				pthread_mutex_unlock( &mutex3 );
					
				server = gethostbyname(IPAddress);
				bzero((char*) &cli_addr[i], sizeof(cli_addr[i]));
				cli_addr[i].sin_family = AF_INET; 
				bcopy((char*) server->h_addr, (char*) &cli_addr[i].sin_addr.s_addr, server->h_length);	
				cli_addr[i].sin_port = htons(PortNo);

				// checking if the ith peer is not the current peer itself
				// no need to connect to itself
				if(PortNo != Port)	
				{	
					while(connect(srvSocket, (struct sockaddr*) &cli_addr[i], sizeof(cli_addr[i])) < 0);
					pthread_mutex_lock( &mutex3 );
						peerList[i].st = connected; // showing that the ith peer is connectable
					pthread_mutex_unlock( &mutex3 );
				
					while( read(srvSocket, buffer, 19) < 0 );// checking if the peer has all the chunks
					
					if(buffer[0] != 'c') // no need to go in and transfer the chunk coz it has already all the chunks
					{
						sprintf(buffer, "%d", totChunks);
						while( write(srvSocket, buffer, 19 ) < 0 ); // Sending the total chunks in file value
						sprintf(buffer, "%d", fileSize);
						while( write(srvSocket, buffer, 19 ) < 0 ); // Sending the file size
						
						getChunkPeerInfo( srvSocket, i );
						cIdx = getLeastRepChunkIdx(); // getting the least replication chunk index 	
						j = 0;
				
						// Asking the peer if the x chunk is needed
						sprintf(buffer, "%d", cIdx ); // getting next chunk (say x)
						while( write(srvSocket, buffer, 19) < 0);
						while( read(srvSocket, buffer, 19) < 0 ); // reading the response
		
						if(buffer[0] == 'y') // 'y' = yes. It means transfer the chunk
						{
							if(cIdx == totChunks-1) cSize = lastChunkSize;
							else cSize = chunkSize;
			
							pSize = 0;
							pIdx = 0;				
							//printf("Transferring Chunk# %d to Port: %d\n", cIdx+1, PortNo); 
							
							fileBuf = (char* ) malloc(cSize*sizeof(char));	
							pthread_mutex_lock( &mutex7 );
								//fp1 = fopen( fileN, "r" );
								fileMaxChk++;
								if(fp1 == NULL)
								{ perror(fileN); printf("%d\n", fileMaxChk); }
								fseek(fp1, (cIdx*chunkSize), SEEK_SET);
								fread(fileBuf, sizeof(char), cSize, fp1);
								fseek(fp1, 0, SEEK_SET);
								//fclose(fp1);
							pthread_mutex_unlock( &mutex7 );

							while(pIdx != (cSize/packetSize + 1 ) )
							{
								if(pIdx == cSize/packetSize) pSize=cSize%packetSize;
								else pSize = packetSize;							

								//fileBuf = (char* ) malloc(pSize*sizeof(char));	
								//pthread_mutex_lock( &mutex7 );
								//	fp = fopen(fileN, "r");
								//	if(fp1 == NULL) perror(fileN);
								//	fseek(fp1, (cIdx*chunkSize + pIdx*packetSize), SEEK_SET);
								//	fread(fileBuf, sizeof(char), pSize, fp1);
								//	fseek(fp1, 0, SEEK_SET);
								//	fclose(fp);
								//pthread_mutex_unlock( &mutex7 );
								//while(read(srvSocket, buffer, 19) < 0);
								while(write(srvSocket, fileBuf+(pIdx*packetSize), pSize) < 0); // sending the chunk to the peer 
								while(read(srvSocket, buffer, 19) < 0);
								//free(fileBuf);
								pIdx++;
							}
							//fclose(fp1);
							//pthread_mutex_unlock( &mutex7 );
							free(fileBuf);

							// Write complete confirmation
							buffer[0] = 's'; // 's' = successfull transferred a chunk
							while( write( srvSocket, buffer, 19) < 0);
							//printf("Chunk successfully transferred to Port# %d\n", PortNo);

							pthread_mutex_lock( &mutex1 );
								chunkRep[i*totChunks + cIdx] = 1;
							pthread_mutex_unlock ( &mutex1 );
						}
	
						//else printf("chunk# %d not required\n", cIdx);			
						pthread_mutex_lock( &mutex2 );
							cRec = chunksRec; 
						pthread_mutex_unlock( &mutex2 );
					}
					else // has all the chunks
					{
						pthread_mutex_lock( &mutex6 );
							peerCMap[i] = 1;
						pthread_mutex_unlock( &mutex6 );
						
						getChunkPeerInfo( srvSocket, i);
						printf("Peer (Port# %d) has received all the chunks\n", PortNo);
					}
				}
				close(srvSocket);
	
			}
			else pthread_mutex_unlock( &mutex2 );
		}
		else pthread_mutex_unlock( &mutex6 );
		i++;
		pthread_mutex_lock( &mutex7 );
			fclose(fp1);
		pthread_mutex_unlock( &mutex7 );
	}	
}	

// This thread is constantly accepting chunks from any peer
void * acceptSoc(void * returnVal)
{
	int cliSocket = -1;
	int srvSocket = -1;
	int pSize = 0;
	int pIdx = 0;
	int cRec = 0;
	struct sockaddr_in cli_addr;
	socklen_t clientLen;
	int n = 0;	
	int j = 0;
	int index = 0;
	char buffer[20] = "works!!";
	int msg = 0;
	char fileN [10];
	char * fileBuf;
	FILE* fp2;
	int cSize = 0;
	int min = 0;
	int local = 0;
	int present = 0;
	float avg = 0.0;	
	sprintf(fileN, "Result%d.mp3", idx);
	

	while((srvSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0);
	bzero( (char*) &srv_addr, sizeof(srv_addr) );
	srv_addr.sin_family = AF_INET; 
	srv_addr.sin_addr.s_addr = INADDR_ANY; 
	srv_addr.sin_port = htons(Port);	
	
	while(bind(srvSocket, (struct sockaddr* ) &srv_addr, sizeof(srv_addr)) < 0 );
	listen(srvSocket, 5);
	clientLen = sizeof(cli_addr);
	//printf("Entering the accept thread: Port(%d)\n", Port);
	while(1)	
	{
		pthread_mutex_lock( &mutex7 );
			fp2 = fopen( fileN, "r+" );
		pthread_mutex_unlock( &mutex7 );
			if(fp2 == NULL) perror("nonzeor");
		/*
		if(totChunks == 0 )
		{
			fp2 = fopen( fileN, "w" );
			if(fp2 == NULL) perror("zeor");
		}
		else
		{	
			fp2 = fopen( fileN, "r+" );
			if(fp2 == NULL) perror("nonzeor");
		}
		*/
		while(cliSocket < 0)
		{
			// if the leave() command has asked to stop accepting
			// then break this inner while(1)
			pthread_mutex_lock( &mutex5 );
				if(stopAccepting) break;
			pthread_mutex_unlock( &mutex5 );
			cliSocket = accept( srvSocket, (struct sockaddr *) &cli_addr, &clientLen );
				
		}
		pthread_mutex_lock( &mutex5 );
			if(stopAccepting) break; // break the main while loop
		pthread_mutex_unlock( &mutex5 );

		pthread_mutex_unlock( &mutex2 );
		if(chunksRec == totChunks && totChunks != 0)
		{	
			pthread_mutex_unlock( &mutex2 );
			//printf("Notifying the peer that all the chunks have been received\n");
			buffer[0] = 'c'; // finished receiving all chunks
			
			pthread_mutex_lock( &mutex6 );
				peerCMap[idx] = 1;
			pthread_mutex_unlock( &mutex6 );
			
			while( write(cliSocket, buffer, 19) < 0 ); // letting the peer know that all the chunks has been transferred
			sendChunkPeerInfo( cliSocket );
		}
		else 
		{	
			pthread_mutex_unlock( &mutex2 );
			buffer[0] = 'n';
		
			while( write(cliSocket, buffer, 19) < 0 ); // letting the peer know that NOTT all the chunks have been received by this yet
			
			while( read(cliSocket, buffer, 19) < 0 ); // getting the total chunk of file
			// if instead of the total chunk, the peer receives 'o'
			// then it means the owner of file has left, so updating ownerLeft  
			if(buffer[0] == 'o')
			{
				pthread_mutex_lock( &mutex4 );
					ownerLeft = 1;
				pthread_mutex_unlock( &mutex4 );
				break;		
			}
			// if the peer receives 'l' then it means one of the peer (who is not the owner
			// of file) has left. So need to stop connecting to this peer from now on.
			else if(buffer[0] == 'l')
			{
				while( read(cliSocket, buffer, 19) < 0 ); // getting index of the peer
				pthread_mutex_lock( &mutex3 );
					// setting the status of the connected peer to unknown 		
					peerList[atoi(buffer)].st = unknown; 
				pthread_mutex_unlock( &mutex3 );
			}

			pthread_mutex_lock( &mutex1 );
			if(totChunks == 0) // initializing stuff if the peer is not an actual file owner
			{
					totChunks = atoi(buffer);
					chunkMap = (int* ) calloc(totChunks, sizeof(int));
					chunkRep = (int* ) calloc(totChunks*(max-1), sizeof(int));
					chunkOrder = (int* ) calloc(totChunks, sizeof(int));
					fileSize = totChunks*chunkSize;
				pthread_mutex_unlock( &mutex1 );

				while( read(cliSocket, buffer, 19) < 0 ); // getting the filesize
				pthread_mutex_lock( &mutex1 );
					fileSize = atoi(buffer);
					lastChunkSize = fileSize%(chunkSize*(totChunks-1));
				pthread_mutex_unlock( &mutex1 );
				pthread_mutex_lock( &mutex7 );
					// open a new file to pusht all the file data into that file
					//fp2 = fopen(fileN, "w");
					//fclose(fp2);
				pthread_mutex_unlock( &mutex7 );
			}
			else 
			{
				pthread_mutex_unlock( &mutex1 );
				while( read(cliSocket, buffer, 19) < 0 ); // getting the fileSize
			}
			
			sendChunkPeerInfo( cliSocket );

			// reading the index of the chunk that is being offered
			while( read(cliSocket, buffer, 19) < 0 ); 
				
			pthread_mutex_lock( &mutex1 );
			// checking if the chunk is needed or not
			if(chunkMap[(index=atoi(buffer))] == 0)
			{
				pthread_mutex_unlock( &mutex1 );

				if(index == totChunks-1) cSize = lastChunkSize;
				else cSize = chunkSize;
				
				buffer[0] = 'y';
				while( write(cliSocket, buffer, 19) < 0 ); // responding with YES!
				
				pSize = 0;
				pIdx = 0;		
				
				fileBuf = (char* ) malloc(cSize*sizeof(char));	
				//pthread_mutex_lock( &mutex7 );
				//fp2 = fopen(fileN, "r");
				while( pIdx != (cSize/packetSize + 1))
				{
					if( pIdx == cSize/packetSize ) pSize = cSize%packetSize;
					else pSize = packetSize;
	
					//fileBuf = (char* ) malloc(pSize*sizeof(char));	
					// reading the actual chunk
					//while(write(cliSocket, buffer, 19) < 0 );
					while( read(cliSocket, fileBuf+(pIdx*packetSize), pSize) < 0 );
					while(write(cliSocket, buffer, 19) < 0 );
					//pthread_mutex_lock( &mutex7 );
					//	// open a new file to pusht all the file data into that file
					//	fp2 = fopen(fileN, "r+");
					//	if(fp2 == NULL) perror(fileN);
					//	fseek(fp2, (index*chunkSize + pIdx*packetSize), SEEK_SET);
					//	fwrite( fileBuf, sizeof(char), pSize, fp2 ); 
					//	fseek(fp2, 0, SEEK_SET);
					//	if(fclose(fp2) != 0) printf("BADDDD\n");
					//pthread_mutex_unlock( &mutex7 );
					//free(fileBuf);
					pIdx++;
				}
				//if(fclose(fp2) != 0) perror("BADDD");
				//pthread_mutex_unlock( &mutex7 );
				pthread_mutex_lock( &mutex7 );
					//fp2 = fopen( fileN, "r+" );
					fileMaxChk++;
					if(fp2 == NULL) 
					{ perror(fileN); printf("%d\n", fileMaxChk); }
					fseek(fp2, (index*chunkSize), SEEK_SET);
					fwrite(fileBuf, sizeof(char), cSize, fp2);
					fseek(fp2, 0, SEEK_SET);
					//fclose(fp2);
				pthread_mutex_unlock( &mutex7 );
				free(fileBuf);

				//printf("Chunk# %d has been received\n", index+1);
				pthread_mutex_lock( &mutex1 );
					// updating chunkMap and chunkOrder with the new chunk info
					chunkMap[index] = 1;
					chunkRep[(idx*totChunks) + index] = 1;	
					chunkOrder[chunksRec] = index;
				pthread_mutex_unlock( &mutex1 );	
			
				pthread_mutex_lock( &mutex2 );
					// update the value of total chunks received
					cRec = ++chunksRec;
				pthread_mutex_unlock( &mutex2 );
				min = minimumReplicationLevel(1);
				local = fractionPresentLocally(1);
				present = fractionPresent(1);
				avg = averageReplicationLevel(1);
				printf("\nFile Received: %d%\r",(cRec*100)/totChunks );
				printf("\nTransfer Status: Minimum Replication Level: %d\r", min);
				printf("\nTransfer Status: Fractions Present Locally: %d Chunks (1 Chunk = %d Bytes)\r", local, chunkSize);
				printf("\nTransfer Status: Total # Of Fractions: %d Chunks (1 Chunk = %d Bytes)\r", present, chunkSize);
				printf("\nTransfer Status: Average Replication Level: %f\r", avg);
				//buffer[0] = 's';
				while( read(cliSocket, buffer, 19) < 0 ); // response from the sender with 's'
			//	break; // break the inner while loop if this has received one chunk
			}
			else 
			{
				pthread_mutex_unlock( &mutex1 );
			
				buffer[0] = 'n';
				while( write(cliSocket, buffer, 19) < 0 ); // sending the response to the sender
				
			}
		}
		cliSocket = -1;
		pthread_mutex_lock( &mutex7 );
			fclose(fp2);
		pthread_mutex_unlock( &mutex7 );
	}
}

// Try to connect to all the peers mentioned in the list
int Peer::join()
{
	FILE * fp;
	int t_success = 1;

	//Socket = socket(AF_INET, SOCK_STREAM, 0);
	//if( socket < 0 )
	//	printf("error in opening the socket");
	
	// initializing all the threads
	pthread_mutex_init( &mutex1, NULL);
	pthread_mutex_init( &mutex2, NULL);
	pthread_mutex_init( &mutex3, NULL);
	pthread_mutex_init( &mutex4, NULL);
	pthread_mutex_init( &mutex5, NULL);
	pthread_mutex_init( &mutex6, NULL);
	pthread_mutex_init( &mutex7, NULL);
	

	// This thread is for reading from the terminal
	if( (t_success = pthread_create( &threads[3], NULL, &readTerminal, NULL)) )
		printf("Thread was not successfully created\n");
	
	// waiting until sender thread finishes----
	pthread_join( threads[3], NULL );
	
	//printf("END\n");	
	// call leave to notify all the peers about leaving 
	// if the owner of the file has left then no need to notify any peer about leaving
	//if(!ownerLeft) leave(); 

	//printf("END1 - owner left? %d\n", ownerLeft);	
	// waiting until accepter thread finishes
	return 1;
}

// Checks if the filename provided exists and is readable
// if the file exists then alloc memory for the file and returns 1 
int insert(char* filename)
{
	//if(fileRead) return 2; // File has been already read and it's stored in the memory
	char fileN[10];
	FILE * fp;	
	int success = 0;	
	struct stat st;
	int t_success = 1;
	
	printf("%s\n", filename);
	sprintf(fileN, "Result%d.mp3", idx);
	fp = fopen( fileN, "w" );
	fclose(fp);

	if(filename == "")
	{
		fileOwner = 0; // no filename passed as an argument
	}
	else
	{
		fileOwner = 1;
		strcpy( (char *)fileName, filename);
		printf("File name: %s\n", filename);	
		while(stat(filename, &st) != 0);
		fileSize = st.st_size;
		if(fileSize != -1)
		{
			totChunks = (fileSize%chunkSize == 0) ? fileSize/chunkSize : (fileSize/chunkSize) + 1;
			lastChunkSize = fileSize%(chunkSize*(totChunks-1));
			chunkMap = (int* ) calloc(totChunks, sizeof(int));
			chunkOrder = (int* ) calloc(totChunks, sizeof(int));
			chunkRep = (int* ) calloc(totChunks*(max-1), sizeof(int));
			//fileBuffer = (char* ) malloc(chunkSize*sizeof(char));
			printf("File size: %d, Chunk size: %d, Total chunks: %d\n", fileSize, chunkSize, totChunks);	
		}
		else printf("INCORRECT FILE NAME\n");	
	}

	// This thread is for loading the chunks of the file
	if( (t_success = pthread_create( &threads[0], NULL, &loadChunk, NULL ) ))
		printf("Thread was not successfull created\n");
	
	// This thread is for accepting socket connections
	if( (t_success = pthread_create( &threads[1], NULL, &acceptSoc, NULL ) ))
		printf("Thread was not successfull created\n");
			
	// This thread is for connecting to the sockets
	if( (t_success = pthread_create( &threads[2], NULL, &connectSoc, NULL)) )
		printf("Thread was not successfull created\n");
	//if(fileOwner) peerC--;	
	// calling join to transfer the file to all the peers
	//join();	

	//IMP: This needs to be implemented LATER
	/*
	fileCount++;
	fileList[fileCount] = filename;
	*/
	pthread_join( threads[0], NULL);
	/*
	pthread_join( threads[0], NULL);
	printf("END2\n");	
	*/
	// deallocating all the memory that are allocated during the process
	free(chunkMap);
	free(chunkOrder);
	free(fileBuffer);

	return 1; // file is readable and it's data is stored in the local buffer "fileBuffer"
}

int Peer::initialize(char * peersFile, char* portNo)
{
	peerList = (Peer *) malloc(6*sizeof(Peer));
	FILE* fp = fopen(peersFile, "r");
	
	char c;
	char str[15] = "test";
	int i = 0;
	int k = 0;
	//init = 2;
	Port = atoi(portNo);
	printf("Port No: %d\n", Port);
	
	if(fp)
	{
		c = (char)fgetc(fp);
		while(i != maxPeers)
		{
			if(c == '\n')
				c = (char)fgetc(fp);
			if(c == EOF)
				break;
			while( c != ' ' && c != '\n' && c != '\0')
			{
				peerList[i].IPAddress[k++] = c;
				c = (char) fgetc(fp);
			}
			peerList[i].IPAddress[k] = '\0';
			
			k =0;
			c = (char) fgetc(fp);
			while( c != ' ' && c != '\n' && c != '\0' )
			{
				str[k++] = c;
				c = (char) fgetc(fp);
			}	
			str[k] = '\0';
			k = 0;
			peerList[i].PortNo = atoi(str);
			peerList[i].index = i;
			printf("IPAddress: %s - PortNo: %d\n", peerList[i].IPAddress, peerList[i].PortNo);
			if(peerList[i].PortNo == Port) idx = i;
			i++;
		}
		max = i;
	}
	fclose(fp);
	peerC = 1;
	peerCMap = (int* ) calloc(max, sizeof(int));
	return i; // returns the number of peers in the file
}

// Status is the class that you populate with status data on the state
// of replication in this peer and its knowledge of the replication
// level within the system.
// The thing required in the Status object is the data as specified in the private section
// The methods shown are examples of methods that we may implement to access such data
// You will need to create methods to populate the Status data.

class Status {
public:
    
private:
    // This is very cheesy and very lazy, but the focus of this assignment
    // is not on dynamic containers but on the BT p2p file distribution

    int   _numFiles;                               // The number of files currently in the system, as viewed by this peer
    float _local[maxFiles];                        // The fraction of the file present locally 
                                                   // (= chunks on this peer/total number chunks in the file)
    float _system[maxFiles];                       // The fraction of the file present in the system 
                                                   // (= chunks in the system/total number chunks in the file)
                                                   // (Note that if a chunk is present twice, it doesn't get counted twice;
                                                   // this is simply intended to find out if we have the whole file in
                                                   // the system; given that a file must be added at a peer, think about why
                                                   // this number would ever not be 1.)
    int   _leastReplication[maxFiles];             // Sum by chunk over all peers; the minimum of this number is the least 
                                                   // replicated chunk, and thus represents the least level of replication
                                                   // of the file
    float _weightedLeastReplication[maxFiles];     // Sum all chunks in all peers; divde this by the number of chunks in the
                                                   // file; this is the average level of replication of the file
};


// Return codes
// This is a defined set of return codes.
// Using enum here is a bad idea, unless you spell out the values, since it is
// shared with code you don't write.

const int errOK             =  0; // Everything good
const int errUnknownWarning =  1; // Unknown warning
const int errUnknownFatal   = -2; // Unknown error
const int errCannotConnect  = -3; // Cannot connect to anything; fatal error
const int errNoPeersFound   = -4; // Cannot find any peer (e.g., no peers in a peer file); fatal
const int errPeerNotFound   =  5; // Cannot find some peer; warning, since others may be connectable

// please add as necessary

#endif
